package cn.admin.configcenter.registry.common.info;

import cn.admin.config.common.Revision;
import cn.admin.config.common.protocol.Swizzle;
import cn.admin.configcenter.configservice.domain.ConfigInfo;
import cn.admin.configcenter.configservice.service.MannulUpdatedDataService;
import cn.admin.configcenter.configservice.util.MD5;
import cn.admin.configcenter.registry.common.config.ConfigSetting;
import cn.admin.configcenter.registry.common.constants.WarningCode;
import cn.admin.configcenter.registry.common.event.EventDispatcher;
import cn.admin.configcenter.registry.common.event.Events;
import cn.admin.configcenter.registry.common.util.ConfigUtil;
import cn.admin.configcenter.registry.common.util.ReadonlyIterator;
import cn.admin.remoting.Connection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ClientConnection {

    private static final Logger LOG = LoggerFactory.getLogger(ClientConnection.class);
    private static final Logger DATAMONITOR = LoggerFactory.getLogger("DATAMONITOR");
    private final boolean isNative;
    private final Connection connection;
    private final String originalHostId;
    private boolean isSupportGzip = false;
    private final ConcurrentHashMap<GroupId,String> subscribers =
            new ConcurrentHashMap<>(16,0.75F,1);
    private final ConcurrentHashMap<GroupId,PubInfo> publishers =
            new ConcurrentHashMap<>(16,0.75F,1);
    private volatile long lastRenewTime = System.currentTimeMillis();
    public final String originalCsIp;
    public final String originalCsRole;

    public ClientConnection(Connection connection) {
        this.isNative = true;
        this.connection = connection;
        this.originalHostId = ConfigUtil.generateHostId(connection);
        this.originalCsIp = ConfigSetting.serverIp;
        this.originalCsRole = ConfigSetting.serverrole;
    }

    public ClientConnection(String _originalHostId, String _originalCsIp, String _originalCsRole) {
        this.isNative = false;
        this.connection = null;
        this.originalHostId = _originalHostId;
        this.originalCsIp = _originalCsIp;
        this.originalCsRole = _originalCsRole;
    }

    public boolean isNative() {
        return this.isNative;
    }

    public String originalHostId() {
        return this.originalHostId;
    }

    public String originalHostIp() {
        return originalHostIp(this.originalHostId);
    }

    public static String originalHostIp(String originalHostId) {
        int idx = originalHostId.indexOf(58);
        return idx >= 0 ? originalHostId.substring(0, idx) : originalHostId;
    }


    public Iterator<GroupId> publisherGroupsIteratorR() {
        return ReadonlyIterator.wrap(this.publishers.keySet().iterator());
    }

    public void updateGroupDataFromConfigService(GroupId group) {
        PubInfo pubInfo = publishers.get(group);
        if (pubInfo != null && pubInfo.data != null) {
            RevisedData data = MannulUpdatedDataService.getInstance().getDataByDataIdAndGroup(
                    group.dataId,group.groupId,group.revision());
            pubInfo.data = data;
        } else {
            LOG.warn("pubInfo is null for group: " + group);
        }
    }

    public GroupId getGroupIdByClientId(String dataId,String clientId) {
        Iterator<Map.Entry<GroupId,PubInfo>> it = publishers.entrySet().iterator();
        GroupId group;
        PubInfo pub;
        do {
            if (!it.hasNext()) {
                return null;
            }
            Map.Entry<GroupId,PubInfo> entry = it.next();
            group = entry.getKey();
            pub = entry.getValue();
        } while (!group.dataId.equals(dataId) || !pub.clientId.equals(clientId));
        return group;
    }

    public String isSubscriber(GroupId group) {
        return this.subscribers.get(group);
    }

    public boolean addPublisher(GroupId group,String clientId) {
        if (!iamHsf(clientId) && !iamNotify(group.dataId)) {
            int nowSize = publishers.size();
            if (nowSize + 1 > ConfigSetting.maxClientConnections) {
                DATAMONITOR.warn("[pub-count-overflow] {} has too many publishers, count:{} threadhold:{}",
                        this, nowSize, ConfigSetting.maxPubCountPerConnection);
                if (ConfigSetting.ignorePubCountOverflow) {
                    DATAMONITOR.warn("[pub-count-ignore] {} has too many publishers, count:{} threadhold:{}",
                            this, nowSize, ConfigSetting.maxPubCountPerConnection);
                    return false;
                }
            }
        }

        PubInfo pub = publishers.get(group);
        if (pub != null) {
            if (pub.clientId.equals(clientId)) {
                return true;
            } else {
                WarningCode.CLIENT_RE_PUBLISH.warning("only one publisher allowed in connection+group. "
                        + this + ", " + group + ", " + clientId);
                return false;
            }
        } else {
            pub = new PubInfo(group,clientId);
            publishers.put(group,pub);
            return true;
        }
    }

    public void removePublisher(GroupId group,String clientId) {
        publishers.remove(group);
        LOG.warn("[invalidate-publisher] " + group + " in " + this);
        EventDispatcher.fireEvent(new Events.NonPersistPublisherRemovedEvent(this,group));
    }

    public void publishData(GroupId group, Swizzle data, Revision newRev) {
        PubInfo pub = publishers.get(group);
        if (pub == null) {
            LOG.error("########## publisher not found when pub data.");
        } else {
            Revision currentRev = pub.data.getRevision();
            if (newRev.compareTo(currentRev) < 0) {
                LOG.warn("client revision lower than server " +
                        newRev + "<=" + currentRev + ", " + group + ", " + this);
            } else {
                Serializable tmp;

                try {
                    tmp = (Serializable)data.extract();
                } catch (Exception e) {
                    tmp = data;
                }

                if (tmp != null && tmp.getClass() != String.class) {
                    WarningCode.PUBLISH_NON_TEXT.warning(tmp.getClass().getName() + " from " + this);
                } else {
                    String str = (String)tmp;
                    if (!this.iamHsf(pub.clientId) && !this.iamNotify(group.dataId)
                            && str != null && str.length() > ConfigSetting.maxPubDataSizeInBytes) {
                        DATAMONITOR.warn("[pub-datasize-overflow] {} {} pub data size : {}B is large than {}B",
                                this, group, str.length(), ConfigSetting.maxPubDataSizeInBytes);
                        if (ConfigSetting.ignorePubSizeOverflow) {
                            DATAMONITOR.warn("[pub-datasize-ignore] {} {} pub data size : {}B is large than {}B",
                                    this, group, str.length(), ConfigSetting.maxPubDataSizeInBytes);
                            return;
                        }
                    }
                    String HSF_HEADER = "hsf://";
                    String DUBBO_HEADER = "dubbo://";
                    if (str != null && str.startsWith(HSF_HEADER)) {
                        str = str.substring(HSF_HEADER.length());
                    }

                    if (str != null && str.startsWith(DUBBO_HEADER)) {
                        str = str.substring(DUBBO_HEADER.length());
                    }

                    ConfigInfo configInfo = new ConfigInfo();
                    configInfo.setDataId(pub.group.getDataId());
                    configInfo.setGroup(pub.group.getGroupId());
                    configInfo.setContent(str);
                    if (str != null) {
                        configInfo.setSrcIp(str.split(":")[0]);
                    }
                    configInfo.setMd5(MD5.getInstance().getMD5String(str));
                    String contentFromDiamond = MannulUpdatedDataService.getInstance().getDataOrCreate(
                            configInfo,pub.group.isMannualUpdated());
                    boolean useDataFromDiamondByMannualUpdate = contentFromDiamond != null &&
                            pub.group.isMannualUpdated();
                    if (useDataFromDiamondByMannualUpdate) {
                        str = contentFromDiamond;
                    }

                    pub.data = new RevisedData(str,newRev);
                    EventDispatcher.fireEvent(new Events.NonPersistDataPublishedEvent(this,
                            group,data));
                }
            }
        }
    }

    public Map<GroupId,RevisedData> getPubData() {
        Map<GroupId,RevisedData> result = new HashMap<>(this.publishers.size());
        for (Map.Entry<GroupId,PubInfo> entry : this.publishers.entrySet()) {
            GroupId group = entry.getKey();
            PubInfo pub = entry.getValue();
            result.put(group,pub.data);
        }
        return result;
    }

    public void renew() {
        this.lastRenewTime = System.currentTimeMillis();
    }

    public long lastRenewTime() {
        return this.lastRenewTime;
    }

    public int pubCount() {
        return this.publishers.size();
    }

    public int subCount() {
        return this.subscribers.size();
    }

    public void setSupportGzip() {
        this.isSupportGzip = true;
    }

    public boolean isSupportGzip() {
        return this.isSupportGzip;
    }

    public boolean iamHsf(String clientId) {
        return clientId.startsWith("HSFProvider-");
    }

    public boolean iamNotify(String dataId) {
        return dataId.startsWith("NS_");
    }

    public boolean hasPublisher(GroupId group) {
        return this.publishers.get(group) != null;
    }

    public Connection getConnection() {
        return this.connection;
    }

    public void addSubscriber(GroupId group,String clientId) {
        subscribers.put(group,clientId);
        EventDispatcher.fireEvent(new Events.SubscriberAddedEvent(this,group));
    }

    public Swizzle groupData(GroupId group) {
        PubInfo pub = publishers.get(group);
        return null != pub ? (Swizzle) pub.data.getData() : null;
    }

    public boolean isHSFInfo(GroupId group) {
        PubInfo pub = publishers.get(group);
        return pub != null && pub.clientId.startsWith("HSFProvider-");
    }

    public Set<GroupId> getSubscribers() {
        return Collections.unmodifiableSet(this.subscribers.keySet());
    }

    public boolean isNotifyInfo(GroupId group) {
        return group.getDataId().startsWith("NS_");
    }

    public String getPublisherClientId(GroupId group) {
        PubInfo pub = this.publishers.get(group);
        return null != pub ? pub.clientId : null;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Client[");
        sb.append(this.isNative ? "native" : "cluster");
        sb.append(", ");
        sb.append(this.originalHostId).append("]");
        return sb.toString();
    }

}
